<?php 
/**
* Static class to support user authentication.
* Based on session_id, login credentials and storing on disk a match of the two. Login credentials
* are exchanged only once per session, then the server side stores the session_id and the username
* while the client side sotres (in a cookie) only the username. Both must match, and session shall not
* be expired (<1 day) for authentication to succeed. Users have a Linux-style password system.
*@package Core
*@subpackage Auth
*@todo Support authentication with better granularity (now only site-wide supported)
*@author Willy Gardiol <willy@gardiol.org>
*@version 1.0
*/
class CoreAuth
{
	/**
	* Initialize the entire Auth subsystem.
	*/
	static public function Initialize()
	{
		CoreAuth::GetAuthObj();
	}

	/**
	* List of users (array of login name, real name).
	*@return mixed
	*/
	static public function ListUsers()
	{
		$auth = CoreAuth::GetAuthObj();
		return $auth->_ListUsers();
	}

	static public function EditUser( $user_id, $user_data )
	{
		$auth = CoreAuth::GetAuthObj();
		return $auth->_EditUser( $password, $user_id );
	}

	static public function SetPassword( $password, $user_id)
	{
		$auth = CoreAuth::GetAuthObj();
		return $auth->_SetPassword( $password, $user_id );
	}

	/**
	* Create a new user: static wrap.
	*/
	static public function CreateUser( $nickname, $fullname, $email, $password, $admin = true )
	{
		$auth = CoreAuth::GetAuthObj();
		return $auth->_CreateUser( $nickname, $fullname, $email, $password, $admin );
	}

	static public function DeleteUser( $user_id )
	{
		$auth = CoreAuth::GetAuthObj();
		return $auth->_DeleteUser( $user_id );
	}

	/**
	* The central point for Authorization checks...
	* blah blah
	*@return bool
	*/
	static public function Authorize()
	{
		$auth = CoreAuth::GetAuthObj();
		# If admin, always TRUE. IF not, implement here checks based on: Box, Storage item, etc etc!!!
		return $auth->AdminLogged() || $auth->Logged();
	}


	


	static private function GetAuthObj()
	{
		static $auth_obj = false;
		if ( $auth_obj === false )
			$auth_obj = new CoreAuth();
		return $auth_obj;
	}



	private $users = false;
	private $sessions = false;
	private $modified_sessions = false;
	private $modified_users = false;
	private $logged_user = false;

	private function CreateUserID()
	{
		return uniqid("USR");
	}

	private function __construct()
	{
		if ( file_exists( "site_data/passwd" ) )
			$this->users = unserialize(file_get_contents("site_data/passwd"));
		else
		{
			$this->users = array();
			$this->_CreateUser( "admin", "Site Administrator", "set@mail.please", "admin", true, true );
		}

		if ( file_exists( "site_data/sessions" ) )
			$this->sessions = unserialize(file_get_contents("site_data/sessions"));
		else
		{
			$this->sessions = array();
			$this->modified_sessions = true;
		}

		$this->DoLogin( Core::PostVar("login_username", false, "string"),
						Core::PostVar("login_password", false, "string") );
		if ( Core::PostVar( "force_logout", false, "string") )
			$this->DoLogout();

		# Perform some sanity checks.
		$this->DoChecks();
	}

	public function __destruct()
	{
		if ( $this->modified_sessions )
		{
			$this->modified_sessions = false;
			file_put_contents( "site_data/sessions", serialize( $this->sessions ) );
		}
		if ( $this->modified_users )
		{
			$this->modified_users = false;
			file_put_contents( "site_data/passwd", serialize($this->users));
		}
	}

	private function Nick2Id( $nickname )
	{
		foreach ( $this->users as $id => $user )
			if ( $user["nickname"] === $nickname )
				return $id;
		return false;
	}

	private function DoLogin( $try_user, $try_pwd )
	{
		$user_id = $this->Nick2Id( $try_user );
		if ( $user_id )
		{	# If passwords match...
			$user_data = $this->users[$user_id];
			if ( $user_data["passwd"] === crypt( $try_pwd, $user_data["passwd"] ) )
			{
				# Set new session...
				$this->sessions[session_id()] = array(time(),$user_id );
				$this->modified_sessions = true;
				# log in the user and record it in $_SESSION 
				$this->logged_user = $_SESSION["username"] = $user_id;
			}
		} # end login
	}

	/**
	* Remove the current session_id/credentials pair so the current user is logged out.
	*/
	private function DoLogout()
	{	# Unset username
		if ( isset( $_SESSION["username"] ) )
			unset( $_SESSION["username"] );
		$this->logged_user = false;
		# Unset logged session 
		$sid = session_id();
		if ( isset( $this->sessions[$sid] ) )
		{
			unset( $this->sessions[$sid] );
			$this->modified_sessions = true;
		}
	}

	private function DoChecks()
	{
		$timestamp = time();
		$sid = session_id();
		$uid = isset( $_SESSION["username"] ) ? $_SESSION["username"] : false;
		$data = isset( $this->sessions[$sid] ) ? $this->sessions[$sid] : false;

		# check whether we already have a logged in user 
		# (or somebody who pretends to be lgoged in!)
		if (  $uid && $data && isset( $this->users[$uid] ) )
		{	# check if against known logged in sessions
			if (( $data[1] === $uid ) &&
				( $data[0] <= $timestamp ) &&
				( ($data[0]+86400) > $timestamp ) )
				$this->logged_user = $uid;
			else # something is wrong, force logout
				$this->DoLogout();
		}
	}

	/**
	* Create a new user. Only for admin users.
	*@param string $nickname The username to add
	*@param string $fullname The real name for the user (can be blank)
	*@param string $email email for the user (will be crypt-ed)
	*@param string $fullname The real name for the user (can be blank)
	*/
	private function _CreateUser( $nickname, $fullname, $email, $password, $admin = false, $force = false )
	{
		if ( $this->AdminLogged() || $force )
		{
			foreach ( $this->users as $user )
				if ( $user["nickname"] === $nickname )
					return false;
			$user_id = $this->CreateUserID();
			$this->users[$user_id] = array(
											"nickname" => $nickname,
											"passwd" => crypt( $password ),
											"email" => $email,
											"admin" => $admin,
											"fullname" => $fullname );
			$this->modified_users = true;
			return $user_id;
		}
		return false;
	}

	private function _DeleteUser( $user_id )
	{
		if ( $this->AdminLogged() || ( $user_id === $this->logged_user ) )
		{
			unset( $this->users[$user_id] );
			$this->modified_users = true;
			return true;
		}
		return false;
	}

	private function _ListUsers()
	{
		$data = array();
		if ( $this->Logged() )
		{
			$admin = $this->AdminLogged();

			foreach ( $this->users as $id => $user )
			{
				if ( $admin || ($this->logged_user===$id) )
				{
					$data[$id] = $user;	
					unset( $data[$id]["passwd"] );
				}
			}
		}
		return $data;
	}

	private function _SetPassword( $password, $user_id )
	{
		if ( $user_id === false )
			$user_id = $this->logged_user;

		if ( isset( $this->users[$user_id] ) )
		{
			if ( $password && ($this->AdminLogged() || ( $user_id === $this->logged_user )) )
			{
				$this->users[ $user_id ]["passwd"] = crypt( $password );
				$this->modified_users = true;
				return true;
			}
		}
		return false;
	}

	private function _EditUser( $user_id, $user_data )
	{
		if ( $this->AdminLogged() || ( $user_id === $this->logged_user ))
		{
			foreach ( $this->users[$user_id] as $k => $old )
			{
				if ( $k !== "passwd" )
				{
					if ( isset( $user_data[$k] ) && ($user_data[$k] !== $old) )
					{
						$this->users[$user_id][$k] = $user_data[$k];
						$this->modified_users = true;
					}
				}
				else
					$this->_SetPassword( $user_data[$k], $user_id );
			}
			return true;
		}
		return false;
	}

	private function Logged()
	{
		return $this->logged_user !== false;
	}

	private function AdminLogged()
	{
		if ( $this->Logged() )
			return $this->users[ $this->logged_user ]["admin"];
		return false;
	}
}
?>